using System;
using System.Configuration;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Timers;
using System.Web;
using Lync.Widget.Library.Presence;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Signaling;
using log4net;
using Timer = System.Timers.Timer;

namespace Lync.Widget.Library.InstantMessaging
{
    public class UCInstantGroupMessageProvider : IDisposable
    {
        #region Private Fields

        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        private readonly UserEndpoint m_UserEndpoint;
        private InstantMessagingCall m_InstantMessagingCall;
		private InstantMessagingFlow m_InstantMessagingFlow;
        private Conversation m_Conversation;
        private string m_LastReceivedMessageFromLyncClient;
        private bool m_HasSentAcceptationRequest;
        private bool m_WaitingForAcceptation;
        private bool m_ActivityReceived;
        private bool m_ChatCloseRequested;
        private bool m_Disposed;
        private ChatRequestState m_Status;
        // If no message has been received from the lync user since the last message or chat startup, 
        // trigger a timeout and close the chat
        private readonly Timer m_TimeoutTimer;
        private DateTime m_SessionStartTime;
        private readonly int m_TimeoutInSeconds = Convert.ToInt32(ConfigurationManager.AppSettings["GroupChatTimeoutSeconds"]);
        

        #endregion


        #region Public Properties

        public string Session { get; set; }
        public string SipUri { get; set; }
        public string Subject { get; set; }
        public string Username { get; set; }
        public string Page { get; set; }
        public string Title { get; set; }
        public string Topic { get; set; }
        public int Priority { get; set; }
        public bool TakeOver { get; set; }
        public bool IsInInitialMode { get; set; }

        public ChatRequestState Status
        {
            get { return m_Status; }
        }

        #endregion


        #region Events

        public event ChatRequestAcceptanceChangedEventHandler ChatRequestAcceptanceChanged;
        public event MessageReceivedFromLyncClientEventHandler ChatMessageReceivedFromLyncCLient;
        public event InstantMessageProviderExceptionOccuredEventHandler InstantMessageProviderExceptionOccured;
        public event NotificationEventHandler Notification;
        public event ChatClosedEventHandler ChatClosed;
        public event ComposingStateChangedEventHandler ComposingStateChanged;


        protected virtual void OnChatRequestAcceptanceChanged(ChatRequestAcceptanceChangedEventArgs e)
        {
            if (ChatRequestAcceptanceChanged != null)
                ChatRequestAcceptanceChanged(this, e);
        }

        protected virtual void OnChatMessageReceivedFromLyncClient(MessageReceivedFromLyncClientEventArgs e)
        {
            if (ChatMessageReceivedFromLyncCLient != null)
                ChatMessageReceivedFromLyncCLient(this, e);
        }

        protected virtual void OnInstantMessageProviderExceptionOccured(InstantMessageProviderExceptionOccuredEventArgs e)
        {
            if (InstantMessageProviderExceptionOccured != null)
                InstantMessageProviderExceptionOccured(this, e);
        }

        protected virtual void OnNotification(NotificationEventArgs e)
        {
            if (Notification != null)
                Notification(this, e);
        }

        protected virtual void OnChatClosed(EventArgs e)
        {
            if (ChatClosed != null)
                ChatClosed(this, e);

            Dispose();
        }

        protected virtual void OnComposingStateChanged(ComposingStateChangedEventArgs e)
        {
            if (ComposingStateChanged != null)
                ComposingStateChanged(this, e);
        }

        #endregion

        #region Constructor

        public UCInstantGroupMessageProvider(CollaborationPlatform collabPlatform,
                                             string session,
                                             string sipuri,
                                             string username,
                                             string page,
                                             string title,
                                             string topic,
                                             int priority)
        {
            Session = session;
            SipUri = sipuri;
            Subject = username;
            Username = username;
            Page = page;
            Title = title;
            Topic = topic;
            Priority = priority;

            //mark that the chat request has not yet been accepted
            m_WaitingForAcceptation = true;

            //have we received any activity from the lync user?
            m_ActivityReceived = false;

            var userEndpointSettings = new UserEndpointSettings(ConfigurationManager.AppSettings["ChatUserSip"],
                                                                ConfigurationManager.AppSettings["LyncServer"])
                                           {
                                               Credential =
                                                   new NetworkCredential(
                                                   ConfigurationManager.AppSettings["ChatUserName"],
                                                   ConfigurationManager.AppSettings["ChatUserPass"],
                                                   ConfigurationManager.AppSettings["ChatUserDomain"]),                                                   
                                               AutomaticPresencePublicationEnabled = true,
                                           };

            m_UserEndpoint = new UserEndpoint(collabPlatform, userEndpointSettings);
            m_TimeoutTimer = new Timer(5000);
            m_TimeoutTimer.Elapsed += _timeoutTimer_Elapsed;
        }

        public void EstablishConnection(bool isInInitialMode)
        {
            IsInInitialMode = isInInitialMode;
            if (m_Status != ChatRequestState.Idle) return;

            Thread.CurrentThread.Priority = ThreadPriority.Highest;

            m_UserEndpoint.BeginEstablish(EndEndpointEstablish, m_UserEndpoint);
            
            m_SessionStartTime = DateTime.Now;
            m_TimeoutTimer.Start();
            m_Status = ChatRequestState.Created;
        }


        #endregion

        #region Public Methods

        public void SendInstantMessage(string message)
        {
            if (m_Status == ChatRequestState.Idle)
            {
                throw new Exception(String.Format("Provider '{0}' for session '{1}' has not established a connection yet", SipUri, Session));
            }

            if (message == "event.chatClosed")
            {
                m_ChatCloseRequested = true;
                SendInstantMessage("Closing chat..");
            }
            else
            {
                if (m_InstantMessagingFlow != null && m_InstantMessagingFlow.State == MediaFlowState.Active)
                {
                    m_LastReceivedMessageFromLyncClient = message;
					m_InstantMessagingFlow.LocalComposingState = ComposingState.Idle;
                    m_InstantMessagingFlow.BeginSendInstantMessage(m_LastReceivedMessageFromLyncClient,
                                                                   SendMessageCompleted, m_InstantMessagingFlow);
					m_InstantMessagingFlow.LocalComposingState = ComposingState.Idle;
                }
                else
                {
                    if (m_InstantMessagingFlow != null)
                    {
                        Log.Error(
                            string.Format(
                                "A message was attempted to be sent while the messagingFlow was in state {0}, for session {1}",
                                m_InstantMessagingFlow.State, Session));
                        OnInstantMessageProviderExceptionOccured(
                        new InstantMessageProviderExceptionOccuredEventArgs(
                            new Exception("The message could not be sent because the MessagingFlow is in state " +
                                          m_InstantMessagingFlow.State)));
                    }
                }
            }
        }

        public void Close()
        {
            m_Status = ChatRequestState.Closed;
        }

        public void Dispose()
        {
            if (m_Disposed) return;           
            
            m_TimeoutTimer.Stop();
            m_TimeoutTimer.Dispose();
            if (m_InstantMessagingCall != null)
            {
                m_InstantMessagingCall.BeginTerminate(InstantMessagingCall_Terminated, m_InstantMessagingCall);
            }
            m_Disposed = true;
        }

        #endregion


        #region Private Methods

        private void _timeoutTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (!m_ActivityReceived && m_SessionStartTime.AddSeconds(m_TimeoutInSeconds) < DateTime.Now)
            {
                SendInstantMessage("The chat session was closed after the timeout of " + m_TimeoutInSeconds +
                                   " seconds had been reached.");
                OnChatRequestAcceptanceChanged(new ChatRequestAcceptanceChangedEventArgs(false));
            }
        }

        private void EndEndpointEstablish(IAsyncResult ar)
        {
            var currentEndpoint = ar.AsyncState as LocalEndpoint;
            try
            {
                //m_UserEndpoint.EndEstablish(ar);
                currentEndpoint.EndEstablish(ar);
                if (!IsInInitialMode)
                {
                    //Establish call
                    var convSettings = new ConversationSettings
                                           {Priority = ConversationPriority.Normal, Subject = Subject};
                    m_Conversation = new Conversation(m_UserEndpoint, convSettings);
                    m_InstantMessagingCall = new InstantMessagingCall(m_Conversation);
                    m_InstantMessagingCall.InstantMessagingFlowConfigurationRequested +=
                        InstantMessagingCall_FlowConfigurationRequested;
                    m_InstantMessagingCall.BeginEstablish(SipUri, null, null, CallEstablishCompleted,
                                                          m_InstantMessagingCall);
                }

            }
            catch (AuthenticationException authEx)
            {
                // AuthenticationException will be thrown when the credentials are invalid.
                Log.Error("AuthenticationException", authEx);
                OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(authEx));

            }
            catch (ConnectionFailureException connFailEx)
            {
                // ConnectionFailureException will be thrown when the endpoint cannot connect to the server, or the credentials are invalid.
                Log.Error("ConnectionFailureException", connFailEx);
                OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(connFailEx));
            }
            catch (InvalidOperationException iOpEx)
            {
                // InvalidOperationException will be thrown when the endpoint is not in a valid state to connect. To connect, the platform must be started and the Endpoint Idle.
                Log.Error("InvalidOperationException", iOpEx);
                OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(iOpEx));
            }
        }

        private void CallEstablishCompleted(IAsyncResult result)
        {
            var instantMessagingCall = result.AsyncState as InstantMessagingCall;
            try
            {
                instantMessagingCall.EndEstablish(result);
            }
            catch (AuthenticationException authEx)
            {
                // AuthenticationException will be thrown when the credentials are invalid.
                Log.Error("AuthenticationException", authEx);
                OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(authEx));
            }
            catch (ConnectionFailureException connFailEx)
            {
                // ConnectionFailureException will be thrown when the endpoint cannot connect to the server, or the credentials are invalid.
                Log.Error("ConnectionFailureException", connFailEx);
                OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(connFailEx));
            }
            catch (InvalidOperationException iOpEx)
            {
                // InvalidOperationException will be thrown when the endpoint is not in a valid state to connect. To connect, the platform must be started and the Endpoint Idle.
                Log.Error("InvalidOperationException", iOpEx);
                OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(iOpEx));
            }
        }

        private void InstantMessagingCall_FlowConfigurationRequested(object sender,
                                                                     InstantMessagingFlowConfigurationRequestedEventArgs e)
        {
            m_InstantMessagingFlow = e.Flow;

            m_InstantMessagingFlow.StateChanged += InstantMessagingFlow_StateChanged;
            m_InstantMessagingFlow.MessageReceived += InstantMessagingFlow_MessageReceived;
            m_InstantMessagingFlow.RemoteComposingStateChanged += InstantMessagingFlow_RemoteComposingStateChanged;
        }

        private void InstantMessagingFlow_RemoteComposingStateChanged(object sender,
                                                                      Microsoft.Rtc.Collaboration.ComposingStateChangedEventArgs
                                                                          composingStateChangedEventArgs)
        {
            if (composingStateChangedEventArgs.ComposingState == ComposingState.Composing)
            {
                m_ActivityReceived = true;
            }

            OnComposingStateChanged(new ComposingStateChangedEventArgs(composingStateChangedEventArgs.ComposingState));
        }

        private void InstantMessagingFlow_StateChanged(object sender, MediaFlowStateChangedEventArgs e)
        {
            if (e.State == MediaFlowState.Active)
            {
                OnNotification(new NotificationEventArgs("Chat initialized!"));
                OnNotification(new NotificationEventArgs("Checking availability..."));
                if (!m_HasSentAcceptationRequest)
                {
                    var initMessage = Username + " has started a chat with you.";

                    if (!String.IsNullOrEmpty(Page))
                    {
                        Page = HttpUtility.UrlDecode(Page, Encoding.UTF8);
                        Page = HttpUtility.HtmlDecode(Page);
                    }

                    var url = !String.IsNullOrEmpty(Title)
                                    ? string.Format("<a href=\"{0}\">{1}</a>", Page, Title) 
                                    : Page;

                    
                    if (!string.IsNullOrEmpty(Page)) initMessage += "<br/>Page: " + url;

                    if (!string.IsNullOrEmpty(Topic)) initMessage += "<br/>Topic: " + Topic;


                    var htmlBytes = Encoding.UTF8.GetBytes(initMessage);
					m_InstantMessagingFlow.LocalComposingState = ComposingState.Idle;
                    m_InstantMessagingFlow.BeginSendInstantMessage(new System.Net.Mime.ContentType("text/html"),
                                                                   htmlBytes, SendMessageCompleted,
                                                                   m_InstantMessagingFlow);
					m_InstantMessagingFlow.LocalComposingState = ComposingState.Idle;
                    m_HasSentAcceptationRequest = true;
                }
            }
            else
            {
                Log.Info(string.Format("MessagingFlow state changed to {0}, for session {1}", m_InstantMessagingFlow.State,
                                       Session));

                if (e.State == MediaFlowState.Terminated)
                {
                    OnChatClosed(new EventArgs());
                }
            }
        }



        private void InstantMessagingFlow_MessageReceived(object sender, InstantMessageReceivedEventArgs e)
        {
            m_ActivityReceived = true;

            var message = e.TextBody;

            var messageSender = new MessageSender
                                    {
                                        Uri = e.Sender.Uri
                                    };

            m_SessionStartTime = DateTime.Now;

            if (m_WaitingForAcceptation)
            {
                if (message.ToLowerInvariant() == "accept")
                {
                    OnChatRequestAcceptanceChanged(new ChatRequestAcceptanceChangedEventArgs(true));
                }
                else if (message.ToLowerInvariant() == "deny")
                {
                    OnChatRequestAcceptanceChanged(new ChatRequestAcceptanceChangedEventArgs(false));
                }
                else
                {
                    OnChatRequestAcceptanceChanged(new ChatRequestAcceptanceChangedEventArgs(true));



                    OnChatMessageReceivedFromLyncClient(new MessageReceivedFromLyncClientEventArgs(message, messageSender));
                }
                m_WaitingForAcceptation = false;
            }
            else
            {
                OnChatMessageReceivedFromLyncClient(new MessageReceivedFromLyncClientEventArgs(message, messageSender));
            }
        }

        private void SendMessageCompleted(IAsyncResult result)
        {
            try
            {
                m_InstantMessagingFlow.EndSendInstantMessage(result);
                //m_InstantMessagingFlow.LocalComposingState = ComposingState.Idle;

                if (m_ChatCloseRequested)
                {
                    OnChatClosed(new EventArgs());
                }
            }
            catch (Exception exception)
            {
                var error = exception.ToString();
            }
        }

        private void InstantMessagingCall_Terminated(IAsyncResult ar)
        {
            m_InstantMessagingCall.EndTerminate(ar);
        }

        #endregion



    }
}